﻿using Algorithm.Check;
using Common;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using static Algorithm.Check.ExtensionForCRC16;

namespace Device
{
    /// <summary>
    /// 继电器类
    /// </summary>
    public class Relay
    {
        //01 10 00 00 00 1A 10 00 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 86 1A
        private bool connected = false;

        private string deviceName = "继电器控制";
        private SerialPort serialPort;
        private byte[] recvData;
        private readonly object locker = new object();

        private ManualResetEventSlim responseCompletedEvent = new ManualResetEventSlim(false);

        /// <summary>
        /// crc测试方法
        /// </summary>
        public void CRC16Test()
        {
            byte[] data = { 0x01, 0x10, 0x00, 0x00, 0x00, 0x08, 0x10, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, };

            var dataWithCRC = data.WithCRC16(CRC16Type.Modbus);
        }

        /// <summary>
        /// CRC校验
        /// </summary>
        /// <param name="data"> </param>
        /// <returns> </returns>
        public byte[] WithCRC16(byte[] data)
        {
            return data.WithCRC16(CRC16Type.Modbus).ToArray();
        }

        /// <summary>
        /// 串口连接
        /// </summary>
        /// <param name="port"> </param>
        /// <exception cref="Exception"> </exception>

        [Conditional("DEVICE")]
        public void Connect(string port)
        {
            if (connected) return;

            serialPort = new SerialPort();
            serialPort.BaudRate = 9600;
            serialPort.PortName = port;

            ManualResetEventSlim openCompletedEvent = new ManualResetEventSlim(false);
            Task.Run(() =>
            {
                serialPort.Open();
                openCompletedEvent.Set();
            });
            if (!openCompletedEvent.Wait(2000))
            {
                throw new Exception("继电器端口异常");
            }
            serialPort.DataReceived += SerialPort_DataReceived;

            connected = true;
        }

        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            lock (locker)
            {
                Thread.Sleep(100);
                var sp = sender as SerialPort;
                int bytesToRead = serialPort.BytesToRead;
                if (bytesToRead > 0)
                {
                    recvData = new byte[bytesToRead];
                    serialPort.Read(recvData, 0, bytesToRead);
                    Mylog.Debug($"{deviceName}  [{BitConverter.ToString(recvData).Replace("-", " ")}]");
                    responseCompletedEvent.Set();
                }
            }
        }

        [Conditional("DEVICE")]
        private void SendCmd(byte[] cmd)
        {
            Mylog.Debug($"{deviceName} [{cmd}]");
            serialPort.Write(cmd, 0, cmd.Length);
        }

        //01 10 00 09 00 09 D0 0D
        private byte[] okRecv = { 0x01, 0x10, 0x00, 0x00, 0x00, 0x1A, 0x41, 0xC2 };

        /// <summary>
        /// 此函数支持持同时打开一个DO,其他DO是off状态
        /// </summary>
        /// <param name="relayNo"> </param>
        /// <param name="value">   </param>
        /// <exception cref="Exception"> </exception>
        [Conditional("DEVICE")]
        public void SetDO(int relayNo, bool value = true)
        {
            int no = relayNo - 1;
            byte[] cmd = {01,0x10,00,00,00,26,0x10,      //头部固定
                00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
                00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
                00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
                00,00,00,00,
            };

            if (value)
            {
                int byteIndex = (no / 16 + 1) * 2 - (no % 16 > 7 ? 1 : 0);
                int bitIndex = (no % 8);
                cmd[6 + byteIndex] = (byte)(0x01 << bitIndex);
            }

            var cmdSend = WithCRC16(cmd);
            Mylog.Info($"RelaySetDo[{relayNo}]Value[{value}]Cmd[{BitConverter.ToString(cmdSend).Replace("-", " ")}]");
            var recv = SendCmdAndWaitReturn(cmdSend);

            if (!recv.SequenceEqual(okRecv))
            {
                throw new Exception("继电器返回数据校验异常");
            }
        }

        /// <summary>
        /// 同时打开或关闭两个继电器
        /// </summary>
        /// <param name="relayNo1"> </param>
        /// <param name="relayNo2"> </param>
        /// <param name="value">    </param>
        /// <exception cref="Exception"> </exception>
        public void SetDO(int relayNo1, int relayNo2, bool value = true)
        {
            byte[] cmd = {01,0x10,00,00,00,26,0x10,      //头部固定
                00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
                00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
                00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,
                00,00,00,00,
            };

            if (value)
            {
                int no = relayNo1 - 1;
                int byteIndex = (no / 16 + 1) * 2 - (no % 16 > 7 ? 1 : 0);
                int bitIndex = (no % 8);
                cmd[6 + byteIndex] |= (byte)(0x01 << bitIndex);
            }
            if (value)
            {
                int no = relayNo2 - 1;
                int byteIndex = (no / 16 + 1) * 2 - (no % 16 > 7 ? 1 : 0);
                int bitIndex = (no % 8);
                cmd[6 + byteIndex] |= (byte)(0x01 << bitIndex);
            }

            var cmdSend = WithCRC16(cmd);
            Mylog.Info($"RelaySetDo[{relayNo2}]Value[{value}]Cmd[{BitConverter.ToString(cmdSend).Replace("-", " ")}]");

            int errTime = 0;
            for (int i = 0; i < 3; i++) //重试3次
            {
                try
                {
                    var recv = SendCmdAndWaitReturn(cmdSend);
                    Mylog.Info($"RelaySetDoReturn[{BitConverter.ToString(recv).Replace("-", " ")}]");
                    if (!recv.SequenceEqual(okRecv))
                    {
                        throw new Exception("继电器返回数据校验异常");
                    }
                    return;
                }
                catch (Exception ex)
                {
                    Mylog.Error(ex, ex.Message + $"重试{errTime++}");
                }
            }
            if (errTime == 3)
            {
                throw new Exception("继电器通信异常");
            }
        }

        private byte[] SendCmdAndWaitReturn(byte[] cmd)
        {
            Mylog.Debug($"{deviceName} + Cmd[{BitConverter.ToString(cmd).Replace("-", " ")}]");
            recvData = null;
            responseCompletedEvent.Reset();
            serialPort.Write(cmd, 0, cmd.Length);

            if (!responseCompletedEvent.Wait(3000))
            {
                throw new Exception("继电器通信超时");
            }

            return recvData;
        }
    }
}